
;; -------- Config for company-mode --------
;; via: LazyCat & seagle0128
(use-package company
  :diminish company-mode
  :defer t
  :defines
  (company-dabbrev-ignore-case company-dabbrev-downcase)
  :commands
  company-cancel
  :bind
  (("M-/" . hippie-expand)
   ("C-M-i" . company-complete)
   :map company-mode-map
   ("<backtab>" . company-yasnippet)
   :map company-active-map
   ("C-p" . company-select-previous)
   ("C-n" . company-select-next)
   ;; ("<tab>" . company-complete-common-or-cycle)
   ("<tab>" . smarter-yas-expand-next-field-complete)
   ("<backtab>" . p1uxtar-company-yasnippet)
   :map company-search-map
   ("C-p" . company-select-previous)
   ("C-n" . company-select-next))
  :hook
  (;; ((after-init) . global-company-mode)
   (prog-mode . company-mode)
   (conf-mode . company-mode)
   (eshell-mode . company-mode)
   ((ess-mode inferior-ess-mode) . company-mode))
  :init
  (defun p1uxtar-company-yasnippet ()
    "Hide the current completeions and show snippets."
    (interactive)
    (company-cancel)
    (call-interactively 'company-yasnippet))
  (defun smarter-yas-expand-next-field-complete ()
    "Try to `yas-expand' and `yas-next-field' at current cursor position.
If failed try to complete the common part with `company-complete-common'"
    (interactive)
    (if yas-minor-mode
        (let ((old-point (point))
              (old-tick (buffer-chars-modified-tick)))
          (yas-expand)
          (when (and (eq old-point (point))
                     (eq old-tick (buffer-chars-modified-tick)))
            (ignore-errors (yas-next-field))
            (when (and (eq old-point (point))
                       (eq old-tick (buffer-chars-modified-tick)))
              (company-complete-common-or-cycle))))
      (company-complete-common-or-cycle)))
  (setq company-tooltip-align-annotations t
        company-tooltip-limit 12
        company-echo-delay (if (display-graphic-p) nil 0)
        company-minimum-prefix-length 1
        company-require-match nil
        company-global-modes '(not erc-mode message-mode help-mode
                                   gud-mode eshell-mode shell-mode))
  :custom
  (company-abort-manual-when-too-short t)
  (company-global-modes '(not dired-mode dired-sidebar-mode))
  :config
  (add-to-list 'company-backends 'company-files)
  (append company-backends '((company-keywords)))
  (use-package company-box
    :diminish company-box-mode
    :hook
    (company-mode . company-box-mode))
  ;; `yasnippet' integration
  (with-no-warnings
    (with-eval-after-load 'yasnippet
      (defun company-backend-with-yas (backend)
        "Add `yasnippet' to company backend."
        (if (and (listp backend) (member 'company-yasnippet backend))
            backend
          (append (if (consp backend) backend (list backend))
                  '(:with company-yasnippet))))
      (defun my-company-enbale-yas (&rest _)
        "Enable `yasnippet' in `company'."
        (setq company-backends (mapcar #'company-backend-with-yas company-backends)))
      ;; Enable in current backends
      (my-company-enbale-yas)
      ;; Enable in `lsp-mode'
      (advice-add #'lsp--auto-configure :after #'my-company-enbale-yas)
      (defun my-company-yasnippet-disable-inline (fun command &optional arg &rest _ignore)
        "Enable yasnippet but disable it inline."
        (if (eq command 'prefix)
            (when-let ((prefix (funcall fun 'prefix)))
              (unless (memq (char-before (- (point) (length prefix)))
                            '(?. ?< ?> ?\( ?\) ?\[ ?{ ?} ?\" ?' ?`))
                prefix))
          (progn
            (when (and (bound-and-true-p lsp-mode)
                       arg (not (get-text-property 0 'yas-annotation-patch arg)))
              (let* ((name (get-text-property 0 'yas-annotation arg))
                     (snip (format "%s (Snippet)" name))
                     (len (length arg)))
                (put-text-property 0 len 'yas-annotation snip arg)
                (put-text-property 0 len 'yas-annotation-patch t arg)))
            (funcall fun command arg))))
      (advice-add #'company-yasnippet :around #'my-company-yasnippet-disable-inline))))

;; ;; yasnippet support for all company backends
;; (defvar company-mode/enable-yas t
;;   "Enable yasnippet for all backends.")
;; (defun company-mode/backend-with-yas (backend)
;;   "Company BACKEND with yas."
;;   (if (or (not company-mode/enable-yas)
;; 	      (and (listp backend)
;; 	           (member 'company-yasnippet backend)))
;;       backend
;;     (append (if (consp backend) backend (list backend))
;; 	        '(:with company-yasnippet))))

;; Better sorting and filtering
(use-package prescient
  :defer t
  :init
  (setq prescient-filter-method 'fuzzy))
(use-package company-prescient
  :hook
  (company-mode . company-prescient-mode)
  :init
  (company-prescient-mode 1)
  (setq company-prescient-sort-length-enable t))

;; add `company-elisp' backend for elisp
(add-hook 'emacs-lisp-mode-hook
	      (lambda ()
	        (require 'company-elisp)
	        (push 'company-elisp company-backends)))

;; -------- company-dict --------
(use-package company-dict
  :ensure t
  :config
  ;; Where to look for dictionary files. Default is ~/.emacs.d/dict
  (setq company-dict-dir (concat user-emacs-directory "dicts/"))
  ;; make company-dict aware of minor mode dictionaries
  ;; (setq company-dict-minor-mode-list '(cdis-mode))
  ;; Optional: if you want it available everywhere
  (add-to-list 'company-backends 'company-dict))

;; -------- company-tab9 --------
(use-package company-tabnine
  :ensure t
  :after company
  :init
  ;; uncomment below: Symbol’s value as variable is void: company-backends
  (add-to-list 'company-backends #'company-tabnine)
  ;; Trigger completion immediately.
  (setq company-idle-delay 0)
  ;; Number the candidates (use M-1, M-2 etc to select completions).
  (setq company-show-numbers t)
  ;; Use the tab-and-go frontend.
  ;; Allows TAB to select and complete at the same time.
  (company-tng-configure-default)
  (setq company-frontends
        '(company-tng-frontend
          company-pseudo-tooltip-frontend
          company-echo-metadata-frontend))
  ;; The free version of TabNine is good enough,
  ;; and below code is recommended that TabNine not always
  ;; prompt me to purchase a paid version in a large project.
  (defadvice company-echo-show (around disable-tabnine-upgrade-message activate)
    (let ((company-message-func (ad-get-arg 0)))
      (when (and company-message-func
                 (stringp (funcall company-message-func)))
        (unless (string-match "The free version of TabNine only indexes up to" (funcall company-message-func))
          ad-do-it))))
  :config
  ;; calling tab9 & lsp simultaneously
  ;; via: emacs-china.org/t/tabnine/9988/40
  (defun p1uxtar-company-sort-add-tabnine (candidates)
    (if (or (functionp company-backend)
	        (not (and (listp company-backend) (memq 'company-tabnine company-backend))))
	    candidates
      (let ((candidates-table (make-hash-table :test #'equal))
	        candidates-1
	        candidates-2)
	    (dolist (candidate candidates)
	      (if (eq (get-text-property 0 'company-backend candidate)
		          'company-tabnine)
	          (unless (gethash candidate candidates-table)
		        (push candidate candidates-2))
	        (push candidate candidates-1)
	        (puthash candidate t candidates-table)))
	    (setq candidates-1 (nreverse candidates-1))
	    (setq candidates-2 (nreverse candidates-2))
	    (nconc (seq-take candidates-1 2)
	           (seq-take candidates-2 2)
	           (seq-drop candidates-1 2)
	           (seq-drop candidates-2 2)))))
  (add-to-list 'company-transformers 'p1uxtar-company-sort-add-tabnine t)
  ;; ;; `:separate`  separately Sort different backgrounds
  ;; (add-to-list 'company-backends '(company-lsp :with company-tabnine :separate))
  ;; :bind
  ;; (:map company-active-map
  ;;       ("<tab>" . 'company-complete-common-or-cycle)
  ;;       ("RET" . 'company-complete-selection)
  ;;       ("C-n" . 'company-select-next)
  ;;       ("C-p" . 'company-select-previous))
  ;; custom TabNine path:
  ;; (setq company-tabnine-binaries-folder "~/.emacs.d/.TabNine")
  )

;; -------- yasnippet --------
(use-package yasnippet
  :defer t
  :diminish yas-minor-mode
  :init
  (use-package yasnippet-snippets
    :defer t
    :after yasnippet)
  (yas-global-mode 1)
  ;; :hook
  ;; ((prog-mode markdown-mode LaTeX-mode org-mode ess-mode) . yas-minor-mode)
  :config
  (use-package ivy-yasnippet
    :bind
    (("C-c y" . 'ivy-yasnippet)))
  :bind
  (:map yas-keymap
        (;; ("TAB" . 'yas-next-field-or-maybe-expand)
         ;; ([(tab)] . 'yas-next-field-or-maybe-expand)
         ("<return>" . 'company-complete-selection))))

(provide '+completion)
